package BLC

import (
	"../wallet"
	"encoding/hex"
	"fmt"
	"github.com/boltdb/bolt"
	"log"
	"os"
)

//数据库的名字
const dbFile="blockchain.db"

//相当于表的名字
const blockBucket="blocks"

type BlockChain struct{
	Tip []byte  //区块链里面最后一个区块的hash值
	DB *bolt.DB
}

//根据钱的多少找出该给的钱的对应的UTXO
func (bc *BlockChain)FindUnUTXOs(address string,amount int)(int,map[string][]int){
	var unSpentUTXOs=make(map[string][]int)
	var value int

	unUTXOs:=bc.UnUTXOs(address)

	for _,unUTXO:=range unUTXOs{

		value+=unUTXO.Output.Value
		hash:=hex.EncodeToString(unUTXO.TxHash)
		unSpentUTXOs[hash]=append(unSpentUTXOs[hash],unUTXO.Index)

		if value>amount{
			break
		}
	}

	return value,unSpentUTXOs
}

func (bc *BlockChain)UnUTXOs(address string)[]*UTXO{
	//存储未花费输出的交易
	var unspentTXs []*UTXO

	var block *Block

	var spentTXOs=make(map[string][]int)

	iter:=bc.NewIterator()

	for {
		block,iter=iter.Next()
		if iter==nil{
			break
		}
		for _,transaction:=range block.Transactions{
			TxID:=hex.EncodeToString(transaction.ID)
			Outputs:
				for outIdx,out:=range transaction.Vout{
					if spentTXOs[TxID]!=nil{
						for _,spentOut:=range spentTXOs[TxID]{
							//如果索引已经被包含在花费里面了，就可以继续下一个输出索引的遍历
							if outIdx==spentOut{
								continue Outputs
							}
						}
					}

					if out.UnlockedTXOutput(address){
						utxo:=&UTXO{transaction.ID,outIdx,out}
						unspentTXs=append(unspentTXs,utxo)
					}
				}
				if transaction.IsCoinBase()==false{
					for _,in:=range transaction.Vin{

						publicHash:=wallet.Base58Decode([]byte(address))
						ripemd160Hash:=publicHash[1:len(publicHash)-4]

						if in.UnlockTXInput(ripemd160Hash){
							inTXID:=hex.EncodeToString(in.Txid)
							spentTXOs[inTXID]=append(spentTXOs[inTXID],in.Vout)
						}

					}
				}
		}
	}


	return	unspentTXs
}


//创建一个带有创世纪区块的区块链
func CreateGenistBlockChain(address string)*BlockChain{
	var tip []byte
	var genist *Block

	//1.尝试打开或者是创建数据库
	db,err:=bolt.Open(dbFile,0600,nil)
	if err!=nil{
		panic(err)
	}

	err=db.Update(func (tx *bolt.Tx)error{
		//判断表是否存在
		b:=tx.Bucket([]byte(blockBucket))

		if b==nil{ //如果表不存在

			genist=NewGenisBlock(NewCoinBase(address,"中本聪"))
			SeriGenist:=genist.SerilizeBlock()

			fmt.Println("这张表不存在，正在创建一张新的表......")

			//创建表
			b,err:=tx.CreateBucket([]byte(blockBucket))
			if err!=nil{
				log.Panic(err)
			}

			//添加数据到表
			err=b.Put(genist.Hash,SeriGenist)
			if err!=nil{
				log.Panic(err)
			}

			//"l"保存的是最后一个区块的hash值
			err=b.Put([]byte("l"),genist.Hash)
			if err!=nil{
				log.Panic(err)
			}

			tip=genist.Hash
		}else{
			fmt.Println("区块链已经存在!!!")
			tip=b.Get([]byte("l"))
		}

		return nil
	})

	if err!=nil{
		panic(err)
	}

	return &BlockChain{tip,db}
}

//返回一个区块链对象
func BlockChainObject()*BlockChain{
	if dbExists()==false{
		fmt.Println("数据库不存在...")
		os.Exit(1)
	}
	db,err:=bolt.Open(dbFile,0600,nil)
	if err!=nil{
		panic(err)
	}

	var Tip []byte
	err=db.View(func(tx *bolt.Tx)error{
		b:=tx.Bucket([]byte(blockBucket))

		if b!=nil{
			Tip=b.Get([]byte("l"))
		}

		return nil
	})

	return &BlockChain{Tip,db}
}


//产生新的区块，并且加入到区块链中
func (bc *BlockChain) MineNewBlock(txs []*Transaction,Height int64,previewHash []byte){
	if dbExists()==false{
		fmt.Println("数据库不存在。。。")
		os.Exit(1)
	}

	err:=bc.DB.Update(func (tx *bolt.Tx)error{
		b:=tx.Bucket([]byte(blockBucket))
		if b!=nil{

			//验证数字签名
			for _,tx:=range txs{
				if bc.VerifySignature(tx)==false{
					panic("数字签名验证不通过...")
				}
			}

			block:=NewBlock(txs,Height,previewHash)

			err:=b.Put(block.Hash,block.SerilizeBlock())
			if err!=nil{
				panic(err)
			}

			err=b.Put([]byte("l"),block.Hash)
			if err!=nil{
				panic(err)
			}

		}

		return nil
	})

	if err!=nil{
		panic(nil)
	}
}

//遍历整个区块链
func (bc *BlockChain)ViewBlockChain(){
	if dbExists()==false{
		fmt.Println("你的区块链还没有创建。。。")
		os.Exit(1)
	}
	err:=bc.DB.View(func (tx *bolt.Tx)error{
		b:=tx.Bucket([]byte(blockBucket))
		theBlock:=b.Get(bc.Tip)
		i:=1
		for  theBlock!=nil{
			block:=UnSerilizeBlock(theBlock)
			block.Print()
			i+=1
			theBlock=b.Get(block.PreviewHash)
		}

		return nil
	})
	if err!=nil{
		panic(err)
	}
}


//判断数据库是否存在
func dbExists()bool{
	if _,err:=os.Stat(dbFile);os.IsNotExist(err){
		return false
	}else{
		return true
	}
}